/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;

/**
 * 非对称加密工具，公钥加密私钥解密<br>
 * 前后端一致算法，前端采用<a href='https://github.com/ArnaudValensi/node-jsencrypt'>node-jsencrypt</a><br>
 * 静态页面
 *
 * <pre>
 * &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/3.0.0/jsencrypt.min.js"&gt;&lt;/script&gt;
 * &lt;script&gt;
 * 	const encryptor = new JSEncrypt();
 *
 * 	const publicKey= "";
 * 	const privateKey = "";
 *
 * 	encryptor.setPublicKey(publicKey);
 * 	const encrypted = encryptor.encrypt('Hello World');
 *
 * 	encryptor.setPrivateKey(privateKey);
 * 	const decrypted = encryptor.decrypt(encrypted);
 *
 * 	console.log(encrypted)
 * 	console.log(decrypted)
 * &lt;/script&gt;
 * </pre>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
public class RsaUtil {

  @Getter
  public static class PublicPrivateKey {
    private final String publicKeyBase64Str;
    private final String privateKeyBase64Str;

    public PublicPrivateKey(String publicKeyBase64Str, String privateKeyBase64Str) {
      this.publicKeyBase64Str = publicKeyBase64Str;
      this.privateKeyBase64Str = privateKeyBase64Str;
    }

    public static List<String> format2list(String key, String keyType) {
      List<String> lines = new ArrayList<String>();

      int length = key.length(), size = 64;
      int blockSize = length % size == 0 ? length / size : length / size + 1;

      lines.add(StrUtil.format("-----BEGIN {} -----", keyType));
      for (int i = 1; i <= blockSize; i++) {
        lines.add(key.substring((i - 1) * size, Math.min(i * size, length)));
      }
      lines.add(StrUtil.format("-----END {} -----", keyType));

      return lines;
    }

    public static String format(String key, String keyType) {
      return String.join("\n", PublicPrivateKey.format2list(key, keyType));
    }
  }

  private RsaUtil() {}

  public static PublicPrivateKey generator() {
    RSA rsa = SecureUtil.rsa();

    return new PublicPrivateKey(rsa.getPublicKeyBase64(), rsa.getPrivateKeyBase64());
  }

  public static PublicPrivateKey format() {
    PublicPrivateKey key = RsaUtil.generator();

    return new PublicPrivateKey(
        PublicPrivateKey.format(key.getPublicKeyBase64Str(), "PUBLIC KEY"),
        PublicPrivateKey.format(key.getPrivateKeyBase64Str(), "PRIVATE KEY"));
  }

  @SuppressWarnings("all")
  public static void write(String dir) {
    PublicPrivateKey key = RsaUtil.generator();
    String id = IdUtil.simpleUUID();

    FileUtil.writeLines(
        PublicPrivateKey.format2list(key.getPublicKeyBase64Str(), "PUBLIC KEY"),
        StrUtil.format("{}/{}_public.pem", dir, id),
        "utf-8");

    FileUtil.writeLines(
        PublicPrivateKey.format2list(key.getPrivateKeyBase64Str(), "PRIVATE KEY"),
        StrUtil.format("{}/{}_private.pem", dir, id),
        "utf-8");
  }

  public static String decryptStr(String bas64, String privateKeyBase64Str) {
    return RsaUtil.decryptStr(bas64, privateKeyBase64Str, "utf-8");
  }

  public static String decryptStr(String encryptedStr, String privateKeyBase64Str, String charset) {
    RSA rsa = SecureUtil.rsa();
    rsa.setPrivateKey(
        SecureUtil.generatePrivateKey(
            rsa.getPrivateKey().getAlgorithm(), SecureUtil.decode(privateKeyBase64Str)));

    return rsa.decryptStr(encryptedStr, KeyType.PrivateKey, Charset.forName(charset));
  }

  public static String encryptBase64(String data, String publicKeyBase64Str) {
    return RsaUtil.encryptBase64(data, publicKeyBase64Str, "utf-8");
  }

  public static String encryptBase64(String data, String publicKeyBase64Str, String charset) {
    RSA rsa = SecureUtil.rsa();
    rsa.setPublicKey(
        SecureUtil.generatePublicKey(
            rsa.getPublicKey().getAlgorithm(), SecureUtil.decode(publicKeyBase64Str)));
    return rsa.encryptBase64(data, Charset.forName(charset), KeyType.PublicKey);
  }
}
